#include <libdsw3gp/dsw3gp.h>
#include <libdswutility/dsw_sps_pps.h>

#define REC_TIMESCALE 1000

CDsw3gp:: CDsw3gp(LPST_ENV_T pEnv)
: CDswbase(pEnv)
, m_h3gp(MP4_INVALID_FILE_HANDLE)
, m_nVideoTrackId(MP4_INVALID_TRACK_ID)
, m_nFrameDuration(MP4_INVALID_DURATION)
, m_pSps(NULL)
, m_pPps(NULL)
, m_pSpsLen(NULL)
, m_pPpsLen(NULL)
{
}

CDsw3gp::CDsw3gp(LPST_ENV_T pEnv, MP4FileHandle h3gp)
: CDswbase(pEnv)
, m_pEnv(pEnv)
, m_nWidth(0)
, m_nHeight(0)
, m_nFps(0)
, m_nBitrate(0)
, m_nTotalFrames(0)
, m_h3gp(h3gp)
, m_pSps(NULL)
, m_pPps(NULL)
, m_pSpsLen(NULL)
, m_pPpsLen(NULL)
, m_nCurFrameId(1)
{
	LOG4CPLUS_DEBUG(log_dsw, "Create dsw3gp!");
	MP4GetTrackH264SeqPictHeaders(h3gp, 1, &m_pSps, &m_pSpsLen, &m_pPps, &m_pPpsLen);
	m_nWidth = (int)MP4GetTrackVideoWidth(m_h3gp, 1);
	m_nHeight = (int)MP4GetTrackVideoHeight(m_h3gp, 1);
	m_nFps = (int)MP4GetTrackVideoFrameRate(m_h3gp, 1);
	m_nBitrate = (int)MP4GetTrackBitRate(m_h3gp, 1);
	m_nTotalFrames = (int)MP4GetTrackNumberOfSamples(m_h3gp, 1);
}

CDsw3gp::~CDsw3gp()
{
	LOG4CPLUS_DEBUG(log_dsw, "Destroy dsw3gp!");
	if (m_h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(m_h3gp);
}

void CDsw3gp::Close3GPFile()
{
	if (m_h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(m_h3gp);
	m_h3gp = MP4_INVALID_FILE_HANDLE;
}

CDsw3gp* CDsw3gp::createNewZtime(LPST_ENV_T pEnv, bool bLocal, int nCh, char* pZtime)
{
	ST_REC_QUARY_T nRecQuary;
	nRecQuary.bLocal = bLocal;
	nRecQuary.nCh = nCh;
	strcpy(nRecQuary.sZTime, pZtime);
	pEnv->pOnNotifyFunc(PMSG_GET_REC_WITH_TIMEZ, (void*)&nRecQuary, NULL);
	if (!nRecQuary.bFound)
		return NULL;
	MP4FileHandle h3gp = MP4Read(nRecQuary.sFileName);
	if (h3gp == MP4_INVALID_FILE_HANDLE)
		return NULL;
	return new CDsw3gp(pEnv, h3gp);
}

CDsw3gp* CDsw3gp::createNew(LPST_ENV_T pEnv, char * p3GPFileName)
{
	MP4FileHandle h3gp = MP4Read(p3GPFileName);
	if (h3gp == MP4_INVALID_FILE_HANDLE)
		return NULL;
	return new CDsw3gp(pEnv, h3gp);
}

bool CDsw3gp::Reload3GPFile(char* pFileName, int &nWidth, int &nHeight, int &nBitrate, int &nFps, int &nDuration)
{
	if (m_h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(m_h3gp);
	m_h3gp = MP4_INVALID_FILE_HANDLE;
	MP4FileHandle h3gp = MP4Read(pFileName);
	if (h3gp == MP4_INVALID_FILE_HANDLE)
		return false;
	m_h3gp = h3gp;
	MP4GetTrackH264SeqPictHeaders(h3gp, 1, &m_pSps, &m_pSpsLen, &m_pPps, &m_pPpsLen);
	m_nWidth = (int)MP4GetTrackVideoWidth(m_h3gp, 1);
	m_nHeight = (int)MP4GetTrackVideoHeight(m_h3gp, 1);
	m_nFps = (int)MP4GetTrackVideoFrameRate(m_h3gp, 1);
	m_nBitrate = (int)MP4GetTrackBitRate(m_h3gp, 1);
	m_nTotalFrames = (int)MP4GetTrackNumberOfSamples(m_h3gp, 1);
	m_nCurFrameId = 1;
	return true;
}

void CDsw3gp::GetStoreFileResolution(int &nWidth, int &nHeight, int &nBitrate, int &nFps, int &nDuration)
{
	nWidth = m_nWidth;
	nHeight = m_nHeight;
	nBitrate = m_nBitrate;
	nFps = m_nFps;
	nDuration = m_nTotalFrames / m_nFps;
}

void CDsw3gp::SetOffset(int nSeconds)
{
	if (m_h3gp == MP4_INVALID_FILE_HANDLE)
		return;
	int nsid = nSeconds * m_nFps;
	if (nsid == 0)
		nsid = 1;
	if (nsid > m_nTotalFrames)
		nsid = 1;
	int ncount = 0;
	while(MP4GetSampleSync(m_h3gp, 1, nsid) != 1){
		nsid++;
		ncount++;
		if (nsid > m_nTotalFrames)
			nsid = 1;
		if (ncount >= m_nTotalFrames)
			break;
	}
	m_nCurFrameId = nsid;
}

bool CDsw3gp::ReadFrame(unsigned char* pFrame, int &nLen, bool &bKeyFrame)
{
	if (m_h3gp == MP4_INVALID_FILE_HANDLE)
		return false;
	uint8_t *sample = (uint8_t *)pFrame;
	uint32_t this_size = nLen;
	bool isSyncSample;
	bool bRet = MP4ReadSample(m_h3gp,
		1,
		m_nCurFrameId++,
		&sample,
		&this_size,
		NULL,
		NULL,
		NULL,
		&isSyncSample);
	if (bRet){
		memcpy(pFrame, "\x00\x00\x00\x01", 4);
		nLen = this_size;
		if (isSyncSample){
			int nOffset = *m_pSpsLen + 4 + *m_pPpsLen + 4;
			memmove(pFrame + nOffset, pFrame, nLen);
			unsigned char* pPos = pFrame;
			memcpy(pPos, "\x00\x00\x00\x01", 4);
			pPos += 4;
			memcpy(pPos, *m_pSps, *m_pSpsLen);
			pPos += *m_pSpsLen;
			memcpy(pPos, "\x00\x00\x00\x01", 4);
			pPos += 4;
			memcpy(pPos, *m_pPps, *m_pPpsLen);
			nLen += nOffset;
		}
		bKeyFrame = isSyncSample;
	}
	return bRet;
}

bool CDsw3gp::ConvertDataFile23gpFile(char* pTmpDataFile, char* p3GPFileName, bool* pbStop, char* pSaveFrameBuffer)
{
	FILE* fp = NULL;
	MP4FileHandle h3gp = MP4_INVALID_FILE_HANDLE;
	int nVideoTrackId = MP4_INVALID_TRACK_ID;
	int nFileLen;
	ST_RECFILE_HEAD_T nFileHead;
	ST_VIDEO_FRAME_T nVFrame = {0};
	bool bRet = false;

	if (*pbStop)
		goto ERRFORMAT_FILE;
	fp = fopen(pTmpDataFile, "rb+");
	if (fp == NULL){
		LOG4CPLUS_ERROR(log_dsw, "Open [ " << pTmpDataFile << " ] error! err = " << strerror(errno));
		goto ERRFORMAT_FILE;
	}
	fseek(fp, 0, SEEK_SET);
	fseek(fp, 0, SEEK_END);
	nFileLen = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	fread(&nFileHead, 1, sizeof(ST_RECFILE_HEAD_T), fp);
	if (nFileHead.nFileSize != (nFileLen - sizeof(ST_RECFILE_HEAD_T))){
		LOG4CPLUS_ERROR(log_dsw, "Error format tmpfile [ " << pTmpDataFile << " ] !");
		goto ERRFORMAT_FILE;
	}
	if (*pbStop)
		goto ERRFORMAT_FILE;
	h3gp = MP4CreateEx(p3GPFileName, 0, 1, 1, 0, 0, 0, 0);
	if (h3gp == MP4_INVALID_FILE_HANDLE){
		LOG4CPLUS_ERROR(log_dsw, "Can't create 3gp file! file = " << p3GPFileName);
		goto ERRFORMAT_FILE;
	}
	
	MP4SetTimeScale(h3gp, REC_TIMESCALE);
	nVideoTrackId = MP4AddH264VideoTrack(h3gp, REC_TIMESCALE, REC_TIMESCALE / nFileHead.nFps,
		nFileHead.nWidth, nFileHead.nHeight, nFileHead.sSps[1 + 4], nFileHead.sSps[2 + 4], nFileHead.sSps[3 + 4], 3);
	if (nVideoTrackId == MP4_INVALID_TRACK_ID){
		LOG4CPLUS_ERROR(log_dsw, "Can't add video track! file = " << p3GPFileName);
		goto ERRFORMAT_FILE;
	}
	MP4SetVideoProfileLevel(h3gp, 0x0f);
	MP4AddH264SequenceParameterSet(h3gp, nVideoTrackId, (unsigned char*)nFileHead.sSps + 4, nFileHead.nSpsLen - 4);
	MP4AddH264PictureParameterSet(h3gp, nVideoTrackId, (unsigned char*)nFileHead.sPps + 4, nFileHead.nPpsLen - 4);

	nVFrame.pFrame = (void*)pSaveFrameBuffer;
	while((nFileHead.nFileSize > 0) && !*pbStop){
		fread(&nVFrame, 1, sizeof(ST_VIDEO_FRAME_T) - sizeof(void*), fp);
		fread(nVFrame.pFrame, 1, nVFrame.length, fp);
		nFileHead.nFileSize -= (sizeof(ST_VIDEO_FRAME_T) - sizeof(void*) + nVFrame.length);
		unsigned char* pPos;
		int nFrameLen;
		if (nVFrame.is_keyframe){
			pPos = ((unsigned char*)nVFrame.pFrame + nFileHead.nSpsLen + nFileHead.nPpsLen);
			nFrameLen = nVFrame.length - nFileHead.nSpsLen - nFileHead.nPpsLen;
		}
		else{
			pPos = (unsigned char*)nVFrame.pFrame;
			nFrameLen = nVFrame.length;
		}
		int nNewLen = htonl(nFrameLen - sizeof(int));
		memcpy(pPos, &nNewLen, sizeof(int));
		MP4WriteSample(h3gp, nVideoTrackId, pPos, nFrameLen, REC_TIMESCALE / nFileHead.nFps, 0, nVFrame.is_keyframe);
		usleep(1000);
	}
	if (*pbStop)
		goto ERRFORMAT_FILE;
	LOG4CPLUS_DEBUG(log_dsw, "Create a recoder file! file = " << p3GPFileName);
	bRet = true;
ERRFORMAT_FILE:
	if (h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(h3gp);
	if (fp)
		fclose(fp);
	return bRet;
}

bool CDsw3gp::Create3GPFile(char* pFileName, char* pSps, int* pSpsLen, char* pPps, int* pPpsLen, int nFps)
{
	int nWidth, nHeight;
	if (m_h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(m_h3gp);
	h264dec_sps_dim(nWidth, nHeight, pSps, *pSpsLen);
	if ((nWidth <= 0) || (nHeight <= 0)){
		LOG4CPLUS_ERROR(log_dsw, "Invalid dim [ " << nWidth << " X " << nHeight << " ]");
		goto CREATE_ERRFORMAT_FILE;
	}

	m_h3gp = MP4CreateEx(pFileName, 0, 1, 1, 0, 0, 0, 0);
	if (m_h3gp == MP4_INVALID_FILE_HANDLE){
		LOG4CPLUS_ERROR(log_dsw, "Can't create 3gp file! file = " << pFileName);
		goto CREATE_ERRFORMAT_FILE;
	}
	MP4SetTimeScale(m_h3gp, REC_TIMESCALE);

	m_nVideoTrackId = MP4AddH264VideoTrack(m_h3gp, REC_TIMESCALE, REC_TIMESCALE / nFps,
		nWidth, nHeight, pSps[1 + 4], pSps[2 + 4], pSps[3 + 4], 3);
	if (m_nVideoTrackId == MP4_INVALID_TRACK_ID){
		LOG4CPLUS_ERROR(log_dsw, "Can't add video track! file = " << pFileName);
		goto CREATE_ERRFORMAT_FILE;
	}
	m_nFrameDuration = REC_TIMESCALE / nFps;
	MP4SetVideoProfileLevel(m_h3gp, 0x0f);
	MP4AddH264SequenceParameterSet(m_h3gp, m_nVideoTrackId, (unsigned char*)pSps + 4, *pSpsLen - 4);
	MP4AddH264PictureParameterSet(m_h3gp, m_nVideoTrackId, (unsigned char*)pPps + 4, *pPpsLen - 4);

//	MP4GetTrackH264SeqPictHeaders(m_h3gp, 1, &m_pSps, &m_pSpsLen, &m_pPps, &m_pPpsLen);
	m_pPps = (uint8_t**)&pPps;
	m_pPpsLen = (uint32_t*)pPpsLen;
	m_pSps = (uint8_t**)&pSps;
	m_pSpsLen = (uint32_t*)pSpsLen;
	
	return true;
CREATE_ERRFORMAT_FILE:
	if (m_h3gp != MP4_INVALID_FILE_HANDLE)
		MP4Close(m_h3gp);
	m_h3gp = MP4_INVALID_FILE_HANDLE;
	return false;
}

bool CDsw3gp::WriteVideoFrame(bool bKeyFrame, char* pFrame, int nLen)
{
	if (m_h3gp == MP4_INVALID_FILE_HANDLE)
		return false;
	unsigned char* pPos;
	int nFrameLen, nNewLen, nOldLen;
	if (bKeyFrame){
		pPos = (unsigned char*)pFrame + *m_pSpsLen + *m_pPpsLen;
		nFrameLen = nLen - *m_pSpsLen - *m_pPpsLen;
	}
	else{
		pPos = (unsigned char*)pFrame;
		nFrameLen = nLen;
	}
	nNewLen = htonl(nFrameLen - sizeof(int));
	memcpy(&nOldLen, pPos, sizeof(int));
	memcpy(pPos, &nNewLen, sizeof(int));
	MP4WriteSample(m_h3gp, m_nVideoTrackId, pPos, nFrameLen, m_nFrameDuration, 0, bKeyFrame);
	memcpy(pPos, &nOldLen, sizeof(int));
	return true;
}


